/**
 * 分片上传
 */

export type FormDataWithBlobs<T extends string, K1 extends T, K2 extends T> = {
  [Key in T]: Key extends K1 | K2 ? Blob : unknown;
};

interface ChunkInfo {
  key: string;
  chunkIndex: number;
  totalChunks: number;
  data: Blob | unknown;
  fileId: string; // 文件的唯一标识符
}

interface ChunkUploaderOptions {
  uploadAd: string; // 服务端的上传接口地址
  chunkSize?: number; // 分片大小，默认50kb
  maxRetries?: number; // 最大重试次数，默认3次
}

export class ChunkUploader {
  private uploadAd: string;
  private chunkSize: number;
  private maxRetries: number;

  constructor(options: ChunkUploaderOptions) {
    this.uploadAd = options.uploadAd;
    this.chunkSize = options.chunkSize || 50 * 1024; // 默认50kb
    this.maxRetries = options.maxRetries || 3; // 默认3次重试
  }

  /**
   * 上传完整的 FormData
   * @param data FormData 对象
   */
  async upload<T extends string, K1 extends T, K2 extends T>(
    data: FormDataWithBlobs<T, K1, K2>,
    fileId: string
  ): Promise<void> {
    const chunks = this.prepareChunks(data, fileId);

    // 并发上传所有分片
    const uploadResults = await this.uploadChunks(chunks);

    // 重试失败的分片
    const failedChunks = uploadResults.filter((result) => result.status === "rejected");
    if (failedChunks.length > 0) {
      console.warn(`Retrying ${failedChunks.length} failed chunks...`);
      await this.retryFailedChunks(failedChunks as PromiseRejectedResult[], chunks);
    }

    console.log("Upload completed successfully!");
  }

  /**
   * 将 FormData 分片
   */
  private prepareChunks<T extends string, K1 extends T, K2 extends T>(
    data: FormDataWithBlobs<T, K1, K2>,
    fileId: string
  ): ChunkInfo[] {
    const entries = Object.entries(data) as [string, Blob | unknown][];
    const allChunks: ChunkInfo[] = [];
    for (const [key, value] of entries) {
      if (value instanceof Blob) {
        const chunks = this.splitIntoChunks(value, this.chunkSize, key, fileId);
        allChunks.push(...chunks);
      } else {
        allChunks.push({
          fileId,
          key,
          chunkIndex: 0,
          totalChunks: 1,
          data: value,
        });
      }
    }

    return allChunks;
  }

  /**
   * 分片逻辑
   */
  private splitIntoChunks(blob: Blob, chunkSize: number, key: string, fileId:string): ChunkInfo[] {
    const totalChunks = Math.ceil(blob.size / chunkSize);
    const chunks: ChunkInfo[] = [];

    for (let i = 0; i < totalChunks; i++) {
      const start = i * chunkSize;
      const end = Math.min(blob.size, start + chunkSize);
      const chunk = blob.slice(start, end);
      chunks.push({ key, chunkIndex: i, totalChunks, data: chunk, fileId });
    }

    return chunks;
  }

  /**
   * 构建分片上传的请求负载
   */
  private createChunkPayload(chunk: ChunkInfo): FormData {
    const formData = new FormData();
    formData.append("key", chunk.key);
    formData.append("chunk_index", chunk.chunkIndex.toString());
    formData.append("total_chunks", chunk.totalChunks.toString());
    formData.append("file_id", chunk.fileId);
    if (chunk.data instanceof Blob) {
      formData.append("data", chunk.data);
    } else {
      formData.append("data", JSON.stringify(chunk.data));
    }
    return formData;
  }

  /**
   * 并发上传分片
   */
  private async uploadChunks(chunks: ChunkInfo[]): Promise<PromiseSettledResult<Response>[]> {
    return Promise.allSettled(
      chunks.map((chunk) =>
        fetch(this.uploadAd, {
          method: "POST",
          body: this.createChunkPayload(chunk),
        })
      )
    );
  }

  /**
   * 重试失败分片
   */
  private async retryFailedChunks(
    failedChunks: PromiseRejectedResult[],
    allChunks: ChunkInfo[]
  ): Promise<void> {
    const retryableChunks = failedChunks.map((result) => {
      const chunkIndex = (result.reason as any).chunkIndex;
      return allChunks[chunkIndex];
    });

    for (const chunk of retryableChunks) {
      let attempts = 0;
      let success = false;

      while (attempts < this.maxRetries && !success) {
        try {
          await fetch(this.uploadAd, {
            method: "POST",
            body: this.createChunkPayload(chunk),
          });
          success = true;
          console.log(`Chunk ${chunk.chunkIndex} retried successfully.`);
        } catch (error) {
          attempts++;
          console.error(`Retry attempt ${attempts} failed for chunk ${chunk.chunkIndex}`);
        }
      }

      if (!success) {
        console.error(`Chunk ${chunk.chunkIndex} failed after ${this.maxRetries} attempts.`);
        throw new Error(`Upload failed for chunk ${chunk.chunkIndex}`);
      }
    }
  }
}
